package org.quipu.domain;

import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;

@Entity
@Table(name = "TRANSACTIONS")
@NamedQueries({
    @NamedQuery(name = "Transaction.findAll", query = "SELECT t FROM Transaction t"),
    @NamedQuery(name = "Transaction.findById", query = "SELECT t FROM Transaction t WHERE t.id=:id")
})
public class Transaction extends UniqueEntity implements Serializable {

    public enum Type {
        INCOME_INITIAL (0),
        INCOME (1),
        OUTCOME (2),
        TRANSFER (3);

        private final int value;

        Type(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }
    }

    @JoinColumn(name = "ID_SRC_ACCOUNT", referencedColumnName = "ID", nullable = true)
    @ManyToOne
    private Account srcAccount;

    @JoinColumn(name = "ID_DST_ACCOUNT", referencedColumnName = "ID", nullable = true)
    @ManyToOne
    private Account dstAccount;

    @Column(name = "SUM", nullable = false)
    private Double sum;

    @JoinColumn(name = "ID_CURRENCY", referencedColumnName = "ID", nullable = false)
    @ManyToOne
    private Currency currency;

    @JoinColumn(name = "ID_PAYMENT_CURRENCY", referencedColumnName = "ID", nullable = true)
    @ManyToOne
    private Currency paymentCurrency;

    @Column(name = "PAYMENT_SUM", nullable = true)
    private Double paymentSum;

    @JoinColumn(name = "ID_CATEGORY", referencedColumnName = "ID", nullable = true)
    @ManyToOne
    private Category category;

    @Column(name = "DATE")
    @Temporal(javax.persistence.TemporalType.DATE)
    private Date date;

//    @Transient
//    private Date adoptedDate;

    @Column(name = "DESCRIPTION")
    private String description;

    @Column(name = "APPROVED")
    private Boolean approved;

    @OneToMany(mappedBy = "transaction", cascade = CascadeType.REMOVE)
    private Collection<TransactionTagMap> tagMapping;

    public Category getCategory() {
        return category;
    }

    public void setCategory(Category category) {
        this.category = category;
    }

    public Currency getCurrency() {
        return currency;
    }

    public void setCurrency(Currency currency) {
        this.currency = currency;
    }

    public Currency getPaymentCurrency() {
        return paymentCurrency;
    }

    public void setPaymentCurrency(Currency paymentCurrency) {
        this.paymentCurrency = paymentCurrency;
    }

    public Double getPaymentSum() {
        return paymentSum;
    }

    public void setPaymentSum(Double paymentSum) {
        this.paymentSum = paymentSum;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public Account getDstAccount() {
        return dstAccount;
    }

    public void setDstAccount(Account dstAccount) {
        this.dstAccount = dstAccount;
    }

    public Account getSrcAccount() {
        return srcAccount;
    }

    public void setSrcAccount(Account srcAccount) {
        this.srcAccount = srcAccount;
    }

    public Double getSum() {
        return sum;
    }

    public void setSum(Double sum) {
        this.sum = sum;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Boolean getApproved() {
        return approved;
    }

    public void setApproved(Boolean approved) {
        this.approved = approved;
    }

    public Collection<TransactionTagMap> getTagMapping() {
        return tagMapping;
    }

    public void setTagMapping(Collection<TransactionTagMap> tagMapping) {
        this.tagMapping = tagMapping;
    }

    public String getTagsLine() {
        String tagLine = "";
        for(TransactionTagMap ttMap: tagMapping)
            tagLine += ", " + ttMap.getTag().getTitle();
        if(!tagLine.isEmpty())
            tagLine = tagLine.substring(2);
        return tagLine;
    }

    public Collection<Tag> getTags() {
        Collection<Tag> tags = new LinkedList<Tag>();
        for(TransactionTagMap ttMap: tagMapping)
            tags.add(ttMap.getTag());
        return tags;
    }

    public Type getType() {
        if(category == null) {
            if(srcAccount == null)
                return Type.INCOME_INITIAL;
            else
                return Type.TRANSFER;
        } else {
            if(srcAccount != null)
                return Type.OUTCOME;
            else
                return Type.INCOME;
        }
    }

    public boolean isInitialIncome() {
        return getType().equals(Type.INCOME_INITIAL) && category == null;
    }

    public boolean isIncome() {
        return getType().equals(Type.INCOME);
    }

    public boolean isLoan() {
        return getType().equals(Type.TRANSFER) && srcAccount != null && srcAccount.isThirdParty();
    }

    public boolean isOutcome() {
        return getType().equals(Type.OUTCOME);
    }

    public boolean isRepayment() {
        return getType().equals(Type.TRANSFER) && dstAccount != null && dstAccount.isThirdParty();
    }

    public boolean isTransfer() {
        return getType().equals(Type.TRANSFER) && !srcAccount.isThirdParty() && !dstAccount.isThirdParty();
    }

    @Override
    public String toString() {
        return "{ id=" + getId() + "; sum=" + getSum() + "; date=" + getDate() + " }";
    }

    @Override
    public boolean equals(Object tr) {
        if(tr == null)
            return false;
        else
            return tr instanceof Transaction && getId().equals(((Transaction)tr).getId());
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 61 * hash + (getId() != null ? getId().hashCode() : 0);
        return hash;
    }
}
